const loki = require('lokijs');
const uuid = require('uuid');
const lfsa = require('lokijs/src/loki-fs-structured-adapter.js');
const adapter = new lfsa();

async function getDB(tableName) {
    return await new Promise((resolve) => {
        let db = new loki(tableName + ".db", {
            adapter: adapter,
            autoload: true,
            autosave: true,
            autoloadCallback: (err) => {
                var entries = db.getCollection("tasks");
                if (entries === null) {
                    entries = db.addCollection("tasks");
                }
                resolve(db);
            }
        });
    });
}

function LokisStore(opts) {
    opts = opts || {};
    this.tableName = opts.tableName || 'tasks';
    (async ()=> {
        this.db = await getDB(this.tableName);
    })();
}

/**
 * 
 * @param {*} first 
 * @returns 
 */
const takeNextN = function (first) {
    return function (n, cb) {
        var lockId = uuid.v4();
        try {
            const collects = this.db.getCollection('tasks');
            const rows = collects.chain().find({ 'lock': '', })
                .compoundsort([['priority', false], ['$loki', first ? true : false]]).limit(n).data();
            rows.forEach(row => {
                row.lock = lockId;
            });
            collects.update(rows);
            this.db.save();
            setTimeout(() => {
                cb(null, lockId);
            }, 10);
        } catch (e) {
            console.error(e);
            cb(e);
        }
    };
};

/**
 * 
 * @param {*} cb 
 * @returns 
 */
LokisStore.prototype.connect = async function (cb) {
    try {
        const collects = this.db.getCollection('tasks');
        const rets = collects.find({});
        return cb(null, rets.length);
    } catch (e) {
        return cb(e);
    }
};


LokisStore.prototype.getTask = function (taskId, cb) {
    try {
        const collects = this.db.getCollection('tasks');
        const ret = collects.findOne({ id:  taskId, lock: ''});
        if (!ret) {
            return cb();
        }
        cb(null, ret.task);
    } catch (e) {
        return cb(e);
    }
};


LokisStore.prototype.deleteTask = function (taskId, cb) {
    try {
        const collects = this.db.getCollection('tasks');
        collects.findAndRemove('id', taskId);
        this.db.save();
        setTimeout(() => {
            cb();
        }, 10);
    } catch (e) {
        return cb(e);
    }
};

LokisStore.prototype.putTask = function (taskId, task, priority, cb) {
    try {
        const collects = this.db.getCollection('tasks');
        collects.findAndRemove({id: taskId});
        collects.insert({ id: taskId, task: task, priority: priority || 1, lock: '' });
        this.db.save();
        cb();
    } catch (e) {
        return cb('failed_to_insert_task');
    }
};


LokisStore.prototype.takeFirstN = takeNextN(true);
LokisStore.prototype.takeLastN = takeNextN(false);

LokisStore.prototype.getLock = function (lockId, cb) {
    try {
        const collects = this.db.getCollection('tasks');
        const rows = collects.find({lock: lockId});
        const tasks = {};
        rows.forEach(function (row) {
            tasks[row.id] = row.task;
        })
        cb(null, tasks);
    } catch (e) {
        cb(e);
    }
};


/**
 * 
 * @param {*} cb 
 */
LokisStore.prototype.getRunningTasks = function (cb) {
    try {
        const collects = this.db.getCollection('tasks');
        const rows = collects.find({lock: {'$ne': ''}});
        const tasks = {};
        rows.forEach(function (row) {
            tasks[row.lock] = tasks[row.lock] || [];
            tasks[row.lock][row.id] = row.task;
        });
        cb(null, tasks);
    } catch (e) {
        cb(e);
    }
};


LokisStore.prototype.releaseLock = function (lockId, cb) {
    try {
        console.log('detele ', lockId);
        const collects = this.db.getCollection('tasks');
        collects.findAndRemove({lock: lockId});
        this.db.save();
        setTimeout(() => {
            cb();
        }, 10);
        return lockId;
    } catch (e) {
        cb(e);
    }
};

LokisStore.prototype.close = function (cb) {
    try {
        this.db.close();
        cb();
    } catch (e) {
        cb(e);
    }
};

module.exports = LokisStore;